// affixes for a single orthography
// splitting the words into morphems: stem(s), affix(es) and among parts of compound word
// composing words back

#ifndef RS_AFFIX_H
#define RS_AFFIX_H
#include <vector>
#include "rs_table.h"
#include "rs_string.h"
#include "rs_log.h"
#include "rs_regex.h"
using namespace std;


enum affix_flags {
    af_none=0,
    af_group=0x1,
    af_prefix=0x2,
    af_suffix=0x4,
    af_repeatable=0x8,   // for affix_group: several affixes from group can be applied
    af_afreal = 0x10,    // affix realization
    af_alphabet = 0x20,
    af_morph_changing = 0x40,
    af_duplicates=0x100,  // duplicates consonant after short vowel
    af_deletes_e=0x200,  // deletes final -e
    af_changes_y=0x400  // changes -y to -i
};
const unsigned af_common = af_prefix | af_suffix;
    // af_common are possible flags for afcg_flags for common affix data



class affix_cg { // AFFIX Common or Group data
    // either affix group like all inflectional affixes
    // or affix data common for all orthographies
public:
    affix_cg (const rs_string& name, unsigned flags, unsigned priority)
    {
        init (name, flags, priority);
    };
    rs_string get_name() const { return afcg_name; };
    unsigned get_flags() const { return afcg_flags; };
    unsigned get_prio() const { return afcg_prio; };
private:
    void init (const rs_string& name, unsigned flags, unsigned priority)
    {
        afcg_name=name;
        afcg_flags=flags;
        afcg_prio=priority;
    }
    rs_string      afcg_name; // e.g. L"-s", L"-tion", L"un-" or L"inflection"
      // for creating human-readable description
    unsigned       afcg_flags; // bitwise ored values, subset of affix_flags
    unsigned       afcg_prio; // lower numbers are tried first when decomposing word
};
bool name_present_cg (const vector<affix_cg>& cg, const rs_string& name);


class affix {
public:
    affix (unsigned common_ix, unsigned flags, rs_rix orig, rs_string additional) :
          af_acix (common_ix),
          af_enabled(true),
          af_flags(flags),
          af_orig(orig),
          af_additional(additional),
          af_first(0),
          af_size(0)
        {};
    bool is_enabled() const { return af_enabled; };
    unsigned get_first() const { return af_first;};
    unsigned get_size() const { return af_size;};
    unsigned get_acix () const { return af_acix;};
    unsigned get_flags() const { return af_flags;};
    rs_string get_clean_name() const { return af_clean_name;};
    rs_string get_add_data() const { return af_additional;};
    void set_enabled(bool enabled) { af_enabled=enabled;};
    void set_first(unsigned first) { af_first=first;};
    void set_size(unsigned size) { af_size=size;};
    void set_clean_name(const rs_string& value) { af_clean_name=value;};
    void set_afreal_indexes (const vector<unsigned>& sorted_indexes) {
        af_afreal_ix_vec = sorted_indexes;  };
    const vector<unsigned>& get_afreal_index_vec () const {return af_afreal_ix_vec; };
private:
    unsigned       af_acix; // index for corresponding entry in rsg_afcom
    rs_string      af_clean_name; // typical per-orthography affix value
    bool           af_enabled;  // whether to use it when decomposing word
    unsigned       af_flags; // bitwise ored values, specific for each orthography
    unsigned       af_first;  // index of first affix realization in field rsa_afreal
    unsigned       af_size;    // number of affix realizations
    rs_string      af_additional; // additional data in 3rd column
    rs_rix         af_orig;   // line in original table from where it was processed
    vector<unsigned> af_afreal_ix_vec; // affix realizations, in decreasing order of length of "sub" string
};



class afreal { // affix (suffix or prefix) realization
    // e.g. suffix "-s" has affix realizations "s", "es", "ies"
public:
    afreal (const rs_string& add, const rs_string& sub, const rs_string& cond, int flags, unsigned acix) :
            afr_add(add),
            afr_sub(sub),
            afr_re((flags & af_suffix)? cond+L"$" : L"^"+cond),
            afr_flags(flags),
            afr_acix(acix),
            afr_valid(true)
        {
            if (!afr_re.is_valid()) {
                afr_valid = false;
            }
        };
    bool is_valid() { return afr_valid; };
    unsigned get_flags () const { return afr_flags; };
    unsigned get_add_len () const { return unsigned (afr_add.length()); };
    unsigned get_sub_len () const { return unsigned (afr_sub.length()); };
    unsigned get_split_len () const { 
        // so many characters are compared when trying to split a word
        return unsigned (afr_add.length())
            + afr_re.get_length();
    };
    unsigned get_acix () const { return afr_acix; };
    operator rs_string () const;
    bool split (const rs_string& affixed, rs_string& stem) const
    {
        // returns true if word 'affixed' finishes with substring 'suffix.afr_add'
        // and remaining stem matches RE
        // in this case, it generates the stem
        // otherwise, returns false
        return split_or_apply (affixed, stem, true);
    }
    bool apply (const rs_string& stem, rs_string& affixed) const
    {
        // tries to apply affix realization 'suffix' to 'word'
        // if conditions are met, generates the 'affixed' and returns true
        // if conditions are not met, returns false
        return split_or_apply (stem, affixed, false);
    }
private:
// functions
    bool split_or_apply (
        const rs_string& word,
        rs_string& modified,
        bool do_split) const;
//variables
    rs_string afr_add; // string to add to base word
    rs_string afr_sub; // before adding afr_add, remove this part of base word
    rs_regex  afr_re;
    unsigned afr_acix; // index for corresponding entry in rsg_afcom
    bool afr_valid;
    unsigned afr_flags; // conditions when the conversion is possible and additional actions
      // currently, one or more of af_prefix, af_suffix, biwise ored
};

// rs_morphem is not yet used; it will be used when splitting is done in rs_affix
struct rs_morphem {
    // morphem is either stem or affix; more than one stem can be present in a word
    // few examples of decomposing words into morphems
    // warehousekeeper: ware+house+keep-er
    // unhousewifely:   un-house+wife-ly
    // packinghouses:   pack-ing+house-s
    string mr_text; // textual representation; e.g. "ware", "-er", "un-"
    unsigned mr_index; // index in table or vector
    bool mr_stem; // flag: is stem; true for e.g. "ware", "house"; false for affixes
    bool mr_plus; // flag: true if a "+' is to be added before mr_text in word decomposition
};


class rs_affix {
    // affix rules for an orthography like TO (GB, US), or any RO (CS, SD, etc)
public:
    rs_affix () :
        rsa_valid(false),
        rsa_initialized(false)
    {};
    rs_affix (
        const rs_string& affix_cg_fname,
        const rs_string& og_base_name) :
        rsa_valid(false),
        rsa_initialized(false)
    {
        rs_affix_init (affix_cg_fname, og_base_name);
    }
    void rs_affix_init (
        const rs_string& affix_cg_fname,
        const rs_string& og_base_name);
// primitives
    void set_og_name (const rs_string& abbr) { rsa_name = abbr; };
    rs_string get_og_name () const { return rsa_name; };
    rs_string get_alphabet () const { return rsa_alphabet; };
    rs_string get_rules_fname () const;
    static rs_string get_rules_fname (const rs_string& abbreviation);
    bool is_valid() const { return rsa_valid; };
    const affix& get_affix_to_apply (unsigned acix) const { return rsa_affix[rsa_by_com[acix]]; };
    const afreal& get_afreal (unsigned arix) const { return rsa_afreal[arix]; };
    const afreal& get_afreal_to_split (unsigned arix) const { return rsa_afreal[rsa_sg_order[arix]]; };
    unsigned get_sg_start (unsigned agix) const { return rsa_sg_start[agix]; };
    unsigned get_sg_size (unsigned agix) const { return rsa_sg_size[agix]; };
    unsigned get_affix_ix (const rs_string& affix_cg_name) const;
    rs_string show_affixes (
        const rs_string& affixed,
        const rs_string& stem,
        const vector<size_t>& af_vec) const;
    bool split_affixes (
        const rs_string& affixed,
        rs_string& stem,
        vector<size_t>& af_vec,
        const rs_column& text_column,
        unsigned min_stem_len=2,
        unsigned max_level=0) const;
    bool apply_affixes (
        rs_string& composed_word,
        const rs_string& stem,
        const vector<size_t>& af_vec) const;
private:
    bool rsa_valid;
    bool rsa_initialized;
    rs_string rsa_name; // orthography name, e.g. L"TO"
    rs_string rsa_alphabet; // valid characters in dictionary entries
    vector<affix> rsa_affix;
    vector<afreal> rsa_afreal;
    vector<afreal> rsa_mc_afreal; // typical morfem-changing affixes 
 // now follows index 'rsa_by_com' for faster applying affixes
    vector<unsigned> rsa_by_com; // when applying affix with known common index,
      // use this table to get corresponding rsa_affix[i] entry
// now follow three vectors for faster splitting affixes
    vector<unsigned> rsa_sg_order; // order of trying affix realizations when splitting affix
    vector<unsigned> rsa_sg_start; // when trying to split affixes from a group agix,
      // rsa_sg_start[agix] is initial index to rsa_sg_order
      // same size as rsg_afgroup
    vector<unsigned> rsa_sg_size; // when trying to split affixes from a group agix,
      // rgs_sg_size[agix] is number of affix realizations to try
      // same size as rsg_afgroup
// todo: instead of selecting affix realization by searching from the longest to the shortest possible,
// todo (cont) create a transducer based on regular expressions
    vector<affix_cg> rsg_afgroup; // affix groups
    vector<affix_cg> rsg_afcom; // common affix data for all orthographies

    void init_mc_affixes();
    bool parse_affixes(
        const rs_string& affix_fname,
        const vector<affix_cg>& afcom,
        const vector<affix_cg>& afgroup);
    void add_affix_realization (
        const rs_string& col1_text,
        unsigned cur_flag,
        size_t affix_rix,
        unsigned affix_acix,
        bool is_default=false);
    void get_sorted_afreal_indexes (unsigned start_ix, unsigned len, vector<unsigned>& afr_index_vec);
    bool init_affix_cg(const rs_string& affix_cg_fname);
};


#endif
